﻿#light

module PubSim.Customer

open Microsoft.FSharp.Control
open System.Collections.Generic
open System.Threading
open PubSim.Table
open System
open System.Configuration

type Customer(outputQueue : Queue<string>, customerQueue: Queue<Customer>, tables:array<PubSim.Table.Table>) =
    let mutable receivedDrink = false
    let mutable leaveBar = false
    let mutable continueLooping = true
    let lockObj = new Object()
    
    // From configuration file
    let maxNumberOfTables = Convert.ToInt32(ConfigurationManager.AppSettings.Item("MaxNumberOfTables"))
    let maxNumberOfDrinks = Convert.ToInt32(ConfigurationManager.AppSettings.Item("MaxNumberOfDrinks"))
    
    let seed = (int) DateTime.UtcNow.Ticks
    let randomNumber = System.Random(seed)
    let tableNumber = randomNumber.Next(1, maxNumberOfTables)
    let mutable numberOfDrinks = randomNumber.Next(1, maxNumberOfDrinks)
    
    let Log(s : string) =
        outputQueue.Enqueue(sprintf "Customer: %d: %s" Thread.CurrentThread.ManagedThreadId s)
    
    let drinkChooser (tr : int) =
        match tr with
        | 0 -> "coffee"
        | 1 -> "beer"
        | 2 -> "cappuccino"
        | 3 -> "hot chocolate"
        | _ -> "Not a choice"
        
    let customerDrink =
        drinkChooser (randomNumber.Next() % 4)
    
    let decrementDrinkCounter(drinkCount : int) =
        numberOfDrinks <- drinkCount - 1
        numberOfDrinks
    
    /// Return the remains drinks for customer
    let numberOfDrinksLeftFilter(numDrinks: int) =
        match numDrinks with
        | 0 -> 0
        | _ -> decrementDrinkCounter(numDrinks)
    
    /// Does the customer have any drink orders remaining?
    let ContinueServing() =
        match numberOfDrinks with
        | 0 -> false
        | _ -> true
        
    /// If customer has drinks remaining to order then add back to queue
    let AddToQueue(customerQueue: Queue<Customer>, customer:Customer) =
        let addToQueue = ContinueServing()
        outputQueue.Enqueue(sprintf  "Customer: %d add to queue %b" Thread.CurrentThread.ManagedThreadId addToQueue)
        match addToQueue with
            | true  -> customerQueue.Enqueue(customer)
                       leaveBar <- false
            | false -> leaveBar <- true

    /// Do work for beer, lock thread for time it takes to finish drink and add customer
    /// back to queue if they have more drinks left to order.
    let Beer(assignedTableId: int, customerQueue: Queue<Customer>, customer:Customer) =
        outputQueue.Enqueue(sprintf  "Customer: %d drinking beer" Thread.CurrentThread.ManagedThreadId)
        tables.[assignedTableId].AddGlassToTable()
        AddToQueue(customerQueue, customer)
        Log("Taking 500 ms to finish beer")
        Thread.Sleep(new TimeSpan(0,0,0,0,500))
        
    /// Do work for coffee, lock thread for time it takes to finish drink and add customer
    /// back to queue if they have more drinks left to order.
    let Coffee(assignedTableId: int, customerQueue: Queue<Customer>, customer:Customer) =
        outputQueue.Enqueue(sprintf  "Customer: %d drinking coffee" Thread.CurrentThread.ManagedThreadId)
        tables.[assignedTableId].AddCupToTable()
        AddToQueue(customerQueue, customer)
        Log("Taking 1000 ms to finish coffee")
        Thread.Sleep(new TimeSpan(0,0,0,1,0))
    
    /// Do work for cappuccino, lock thread for time it takes to finish drink and add customer
    /// back to queue if they have more drinks left to order.
    let Cappuccino(assignedTableId: int, customerQueue: Queue<Customer>, customer:Customer) =
        outputQueue.Enqueue(sprintf  "Customer: %d drinking cappuccino" Thread.CurrentThread.ManagedThreadId)
        tables.[assignedTableId].AddCupToTable()
        AddToQueue(customerQueue, customer)
        Log("Taking 1500 ms to finish cappuccino")
        Thread.Sleep(new TimeSpan(0,0,0,1,500))
    
    /// Do work for hot chocolate, lock thread for time it takes to finish drink and add customer
    /// back to queue if they have more drinks left to order.
    let HotChocolate(assignedTableId: int, customerQueue: Queue<Customer>, customer:Customer) =
        outputQueue.Enqueue(sprintf  "Customer: %d drinking hot chocolate" Thread.CurrentThread.ManagedThreadId)
        tables.[assignedTableId].AddCupToTable()
        AddToQueue(customerQueue, customer)
        Log("Taking 2000 ms to finish hot chocolate")
        Thread.Sleep(new TimeSpan(0,0,0,2,0))
        
    /// Return the number of drinks remaining
    let NumDrinksLeft() =
        numberOfDrinksLeftFilter numberOfDrinks
        
    let WhatDrink() =
        customerDrink
        
    /// Check the state of receivedDrink and add a lock
    let ReceivedDrink() =
        lock lockObj (fun() ->
            if receivedDrink = true then
                true
            else
                false
        )
        
    member g.GetNextDrink() =
        let drinks = NumDrinksLeft()
        match drinks with
        | 0 -> "done"
        | _ -> drinks.ToString()
    
    member g.FetchDrink() =
        let drinksLeft = g.GetNextDrink()
        let custDrink = WhatDrink()
        outputQueue.Enqueue(sprintf "Customer: %d, drinksLeft %s, custDrink %s" 
            Thread.CurrentThread.ManagedThreadId drinksLeft custDrink)
        custDrink
        
    member g.ReceiveDrink() =
        outputQueue.Enqueue(sprintf "Customer: %d, drink received" Thread.CurrentThread.ManagedThreadId)
        receivedDrink <- true
        ignore()
        
    member g.WhatTable() =
        tableNumber
        
    member g.Start() =
        customerQueue.Enqueue(g)
        continueLooping <- true
        
        Log("Start entered")
        
        outputQueue.Enqueue(sprintf "Customer: %d, numberOfDrinks %d" Thread.CurrentThread.ManagedThreadId numberOfDrinks)
        
        while continueLooping = true do
            outputQueue.Enqueue(sprintf "Customer: %d, Waiting for drink: %s" Thread.CurrentThread.ManagedThreadId customerDrink )
            
            if receivedDrink then
                // Randomly assign the customer to a table here
                let assignedTableId = randomNumber.Next(0,maxNumberOfTables)
                outputQueue.Enqueue(sprintf "Customer: %d, assignedTableId %d" Thread.CurrentThread.ManagedThreadId assignedTableId)
                match customerDrink with
                | "beer"          -> Beer(assignedTableId, customerQueue, g)
                | "coffee"        -> Coffee(assignedTableId, customerQueue, g)
                | "cappuccino"    -> Cappuccino(assignedTableId, customerQueue, g)
                | "hot chocolate" -> HotChocolate(assignedTableId, customerQueue, g)
                | _               -> outputQueue.Enqueue(sprintf  "not a valid drink type")
                receivedDrink <- false
            else  // Still waiting for drink
                Thread.Sleep(1000)

            if leaveBar then
                continueLooping <- false
                outputQueue.Enqueue(sprintf "Customer: %d leaving bar" Thread.CurrentThread.ManagedThreadId)
